package com.qf.security1.conf;

import com.alibaba.fastjson.JSON;
import com.qf.security1.filters.LoginFilter;
import com.qf.security1.filters.VerifyTokenFilter;
import com.qf.security1.pojo.Code;
import com.qf.security1.pojo.R;
import com.qf.security1.utils.MD5Utils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;

//@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    public static void main(String[] args) {
//        String pwd = "123";
//        //按照某种规则，在原始密码的基础上混淆一些额外的字符串 1xx+27a311 -- 加盐[规则+额外字符串的添加]
//        String md5 = MD5Utils.md5("1xx+27a311");
//        System.out.println(md5);

        String hashpw = BCrypt.hashpw("123", BCrypt.gensalt());
        System.out.println(hashpw);

    }

    /**
     * 这里向security提供的是一个密码加密器
     *  如果数据库密码是明文的，提供一个不加密的密码加密器
     *  如果数据库的密码是密文的，提供一个与该密文匹配的密码加密器
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
//        return NoOpPasswordEncoder.getInstance();

        return new BCryptPasswordEncoder();
    }

    /**
     * 认证用户的设定的
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
    }

    /**
     * 资源放行过滤的
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }

    /**
     * 上面两个能做的事情都能做，一般我们开发时，复写这个方法就可以
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //security中默认的页面访问是有特殊标记的，如果页面发送的请求到security中没有这个特殊标记，不允许访问
        //开发中会关闭跨站伪造验证(可以让所有页面正常访问后台资源)
        http.csrf().disable();

        //设置跨域
        http.cors().configurationSource(ccs());

        //配置哪些资源的访问需要登录 （***注意：在指定路径需要认证或者放行时，一定要将大的路径范围放最后）
        http.authorizeRequests()
                //允许该指定资源访问
//                .antMatchers("/user/test1").permitAll()

                //配置什么资源需要进行什么权限控制
                .antMatchers("/user/**").hasAnyRole("P1","P3")
                .antMatchers("/order/**").hasAnyRole("P3","P4")

                //表示所有资源的访问必须登录
                .antMatchers("/**").authenticated();

        http.exceptionHandling()
                //未登录时的响应数据
                .authenticationEntryPoint(new AuthenticationEntryPoint() {
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                        //当访问资源需要登录，但是没有登录时，会执行这个回调
                        R r = R.fail(Code.NOLOGIN, "尚未登录");

                        response.setContentType("application/json;charset=utf-8");
                        response.getWriter().write(JSON.toJSONString(r));
                    }
                })
                //当访问的资源需要权限但是没有权限时，会触发的回调
                .accessDeniedHandler(new AccessDeniedHandler() {
                    @Override
                    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                        R r = R.fail(Code.NOAUTH, "没有访问权限");

                        response.setContentType("application/json;charset=utf-8");
                        response.getWriter().write(JSON.toJSONString(r));
                    }
                });

//        http.formLogin();

        //将登录过滤器添加到security过滤器链中
        http.addFilter(new LoginFilter(authenticationManager()))
                .addFilter(new VerifyTokenFilter(authenticationManager()));

    }

    /**
     * 跨域访问设置
     * @return
     */
    private CorsConfigurationSource ccs() {
        UrlBasedCorsConfigurationSource ubccs = new UrlBasedCorsConfigurationSource();

        CorsConfiguration corsConfig = new CorsConfiguration();
        //允许哪些域的访问, * 表示任何域都可以访问
        corsConfig.setAllowedOrigins(Arrays.asList("*"));
        //允许哪些请求方式的访问
        corsConfig.setAllowedMethods(Arrays.asList("*"));
        //跨域访问时，允许携带的请求头有哪些 (排除cookie的)
        corsConfig.setAllowedHeaders(Arrays.asList("*"));
        //跨域访问时，允许携带的响应头有哪些 (排除cookie的)
        corsConfig.setExposedHeaders(Arrays.asList("*"));

        //允许携带cookie信息
//        corsConfig.setAllowCredentials();

        //任何资源的访问，都按照我们指定的corsConfig配置来进行跨域限定
        ubccs.registerCorsConfiguration("/**",corsConfig);
        return ubccs;
    }
}
